package vista.objetos;

import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.awt.GLJPanel;
import javax.swing.JFrame;

import controlador.ContenedorDeErrores;
import controlador.ControladorEditor;

import eventos.abrir.EventoEditarVentanaRelacion;

import vista.Tema;
import vista.treeDiagrama.NodoRelacion;
import vista.treeDiagrama.NodoTreeDiagrama;

import logica.Entidad;
import logica.EntidadRol;
import logica.ObjetoDiagrama;
import logica.Relacion;
import logica.RelacionException;
import logica.persistencia.GraficableConEtiquetaPersistente;
import logica.persistencia.RelacionGraficablePersistente;
import memento.IMemento;
import memento.MementoCompuesto;
import memento.MementoRelacionGraficable;

public class RelacionGraficable extends GraficableConEtiquetaConAtributos{

	private Relacion relacionDecorada;
	private EstrategiaRelacionGraficable tipo;
	
	public RelacionGraficable(Relacion relacion, EstrategiaRelacionGraficable tipo)
	{
		super();
		this.relacionDecorada = relacion;
		this.relacionDecorada.addObserver(this);
		this.setTipo(tipo);
		this.relacionDecorada.getGraficables().add(this);
		
		inicializarEtiqueta();
	}
	
	private void inicializarEtiqueta()
	{
		getEtiqueta().setText(relacionDecorada.getNombre());
		getEtiqueta().setSize(Tema.getInstancia().getRelacionAnchoMitad() * 2, Tema.getInstancia().getRelacionAltoMitad() * 2);
	}
	
	@Override
	public void agregarseAlCanvas(GLJPanel panel, Font fuente, Color color)
	{	
		super.agregarseAlCanvas(panel, fuente, color);
		
		Iterator<EntidadRolGraficable> itEntidadRol = this.getReferenciasExternas().iterator();
		
		while (itEntidadRol.hasNext())
		{
			EntidadRolGraficable entidadRolGraf = itEntidadRol.next();
			
			entidadRolGraf.setCentroRelacion(this.getPunto());
			entidadRolGraf.agregarseAlCanvas(panel, fuente, color);
		}
	}
	
	@Override
	public void setUbicacionEtiqueta(int nuevoX, int nuevoY)
	{
		Point posicion;
		
		super.setUbicacionEtiqueta(nuevoX, nuevoY);
		
		for (EntidadRolGraficable entidadRolGraf: this.getReferenciasExternas())
		{
			posicion = entidadRolGraf.getEtiqueta().getLocation();

			entidadRolGraf.setUbicacionEtiqueta(posicion.x , 
            		posicion.y);
		}
	}
	
	public String toString(){
		return this.relacionDecorada.getNombre();
	}
	
	@Override
	public ObjetoDiagrama getObjetoDiagramaDecorado(){
		return this.relacionDecorada;
	}
	
	@Override
	public NodoTreeDiagrama getNodoTreeDiagrama() {
		return new NodoRelacion(this);
	}

	public EstrategiaRelacionGraficable getTipo() {
		return tipo;
	}

	public void setTipo(EstrategiaRelacionGraficable tipo) {
		this.tipo = tipo;
	}

	@Override
	public void dibujarse(Object areaDeDibujo) 
	{
		GLAutoDrawable gLDrawable = (GLAutoDrawable) areaDeDibujo; 
		GL2 gl = gLDrawable.getGL().getGL2();
		
		this.dibujarEntidadesRoles(areaDeDibujo);
		this.getTipo().dibujarse(gl, this);
	}

	private void dibujarEntidadesRoles(Object areaDeDibujo)
	{
		Iterator<EntidadRolGraficable> itEntidadRolGraf = this.getReferenciasExternas().iterator();
		
		while(itEntidadRolGraf.hasNext())
			itEntidadRolGraf.next().dibujarse(areaDeDibujo);
	}

	@Override
	public void update(Observable o, Object arg) {
		getEtiqueta().setText(this.relacionDecorada.getNombre());		
	}

	@Override
	public boolean esElGraficableDe(ObjetoDiagrama objeto) {
		return false;
	}

	@Override
	public void borrarseDeLaHoja() 
	{
		if (this.getDiagrama().soyElDiagramaActual())
		{
			super.borrarseDeLaHoja();
			borrarEntidadesRol();
		}
	}

	public void borrarEntidadesRol()
	{
		for (EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			super.getContenedor().remove(entidadRol.getEtiqueta());
	}
		
	private List<EntidadRolGraficable> copiarEntidadesRoles(IContenedorDeGraficable contenedor)
	{
		List<EntidadRolGraficable> entidadRolCopia = new LinkedList<>();
		
		for (EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			entidadRolCopia.add((EntidadRolGraficable)entidadRol.copiarse(contenedor));	
		
		return entidadRolCopia;
	}

	@Override
	/*
	 * Verifica que la relación graficable a copiar no se encuentre en el contenedor. Si se encuentra retorna
	 * esa referencia, sino crea una copia.
	 * 
	 * La copia es de:
	 * 		- los datos gráfico correspondientes a la relación
	 * 		- los atributos de la relación
	 * 		- copia de las entidades roles:
	 * 			- si la entidad que debe referenciar la entidad rol no existe en el contenedor, la crea
	 * 			- sino, se setea la referencia a la entidad existente en el contenedor
	 */
	public GraficableConEtiqueta copiarse(IContenedorDeGraficable contenedor) 
	{
		List<EntidadRolGraficable> entidadesRolCopia = this.copiarEntidadesRoles(contenedor);
		// si había una entidad débil, esta pidio q se copiara la relación.
		if (contenedor.estaElGraficable(this))
			return contenedor.getGraficableCopiaDe(this);
		
		RelacionGraficable relacionCopia = new RelacionGraficable(relacionDecorada, this.getTipo());
		
		try 
		{
			relacionCopia.getPunto().setLocation(this.getPunto());
			relacionCopia.getEtiqueta().setLocation(this.getEtiqueta().getLocation());
			relacionCopia.setEntidadesRol(entidadesRolCopia);
			relacionCopia.setAtributos(this.copiarAtributos(relacionCopia.getPunto()));
		}
		catch (RelacionException | AtributoTipoNoSoportadoException | GraficableException e) { // si salta esta exception es porq se programó mal
			e.printStackTrace();
		}
				

//		diagrama.getDiagrama().agregarObjetoDiagrama(relacionCopia.getObjetoDiagramaDecorado());
		contenedor.agregarGraficable(relacionCopia);
		
		return relacionCopia;
	}
	
	@Override
	public MouseAdapter getEventoEditarGraficable(JFrame ventanaPadre) {
		return new EventoEditarVentanaRelacion(ventanaPadre, ControladorEditor.getInstancia(), 
				ControladorEditor.getInstancia(), this);
	}

	@Override
	public GraficableConEtiquetaPersistente getGraficableConEtiquetaPersistente() { 
		return new RelacionGraficablePersistente(this);
	}

	@Override
	/*
	 * retorna true si forma parte de la relación
	 */
	public boolean tieneLaEntidad(EntidadGraficable entidadGraficable)
	{
		for (EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			if (entidadRol.tieneLaEntidad(entidadGraficable))
				return true;
		
		return false;
	}

	@Override
	/*
	 * dada una entidad que se quiere borrar determina si una relación se debe borrar o no. 
	 * 		- No se debe borrar si:
	 * 			- la entidad no pertene a la relación
	 * 			- la entidad pertenece a la relación, pero es una relación entre 3 o más entidades
	 * 
	 * 		- La relación se debe borrar si:
	 * 			- la entidad pertenece a la relación y es una relación binaria
	 * 
	 * -> si la relación tienen entidades débiles, dice que se puede borrar, pero hay que borrar dicha entidad
	 * tmb
	 */
	public boolean sePuedeBorrar(EntidadGraficable entidadGraficable)
	{
		if (!this.tieneLaEntidad(entidadGraficable))
			return false;
		
		if (this.getReferenciasExternas().size() > 2)
			return false;
		
		return true;
	}

	@Override
	public  IMemento getMementoGenerico() { 
		
		if (getObjetoDiagramaDecorado().getGraficables().size() > 1)
			return new MementoCompuesto(this);
		
		return this.getMementoEspecializado();
	}
	
	@Override
	public IMemento getMementoEspecializado() {
		return new MementoRelacionGraficable(this);
	}
	
	@Override
	public void cambiarEscala(int nro)
	{
		super.cambiarEscala(nro);
		
		for (EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			entidadRol.cambiarEscala(nro);
	}
	
	public void setEntidadesRol(List<EntidadRolGraficable> referenciasExternas) throws RelacionException 
	{
		//si es una relación de EntidadDebil - EntidadFuerte, tiene que ser una relación binaria
		//chequeo si era una relación de EntidadDebil- EntidadFuerte	
		verificarSiEsRelacionDebilFuerte(referenciasExternas);
		setReferenciasExternas(referenciasExternas);
	}
	
	public void verificarSiEsRelacionDebilFuerte(List<EntidadRolGraficable> referenciasExternas) throws RelacionException
	{
		if(this.getReferenciasExternas().size() != 2) //TODO no sé porq puse esto, no le encuentro sentido
			return;
		
		Entidad entidad1 = (Entidad)this.getReferenciasExternas().get(0).getEntidad().getObjetoDiagramaDecorado(), 
				entidad2 = (Entidad)this.getReferenciasExternas().get(1).getEntidad().getObjetoDiagramaDecorado();

		if (entidad1.tieneEstaEntidad(entidad2) || entidad2.tieneEstaEntidad(entidad1))
		{//=> es una relación de EntidadDebil- EntidadFuerte
			if (referenciasExternas.size() != 2)
				throw new RelacionException("Una relación de dependecia entre una entidad débil y una entidad fuerte solo puede ser binaria.");

			Entidad entidad1b = (Entidad)referenciasExternas.get(0).getEntidad().getObjetoDiagramaDecorado(), 
						entidad2b = (Entidad)referenciasExternas.get(1).getEntidad().getObjetoDiagramaDecorado();
			
			if (!entidad1b.equals(entidad1) && !entidad1b.equals(entidad2) || !entidad2b.equals(entidad1) && !entidad2b.equals(entidad2))
				throw new RelacionException("Se está alterando la relación de dependecia entre una entidad débil y una entidad fuerte.");
			
			return;
		}
	}
	
	@Override
	/*
	 * Si es una relación entre dos entidades fuertes ---> se puede borrar
	 * Si es una relación entre una entidad fuerte y una débil ---> no se puede borrar, se debe borrar primero la débil
	 * 
	 */
	public void borrarseDelDiagrama()
	{
		if(this.getDiagrama() == null)
			return;
		
		if (relacionDecorada.esRelacionFuerteDebil())
		{ //busco la relación débil
			if(this.getReferenciasExternas().get(0).getEntidad().soyUnaEntidadGraficableDebil())
				this.getReferenciasExternas().get(0).getEntidad().borrarseDelDiagrama();
			else
				this.getReferenciasExternas().get(1).getEntidad().borrarseDelDiagrama();
		}
		
		try {
			this.getDiagrama().borrarGraficable(this);
		} catch (NoEsObjetoDiagramableException e) {//no debería pasar.
			e.printStackTrace();
		}
	}

	void borrarRelacionDebil()
	{
		if(this.getDiagrama() == null)
			return;

		try {
			this.getDiagrama().borrarGraficable(this);
		} catch (NoEsObjetoDiagramableException e) {//no debería pasar.
			e.printStackTrace();
		}
	}
	
	@Override
	/*
	 * si borro una entidad tiene que ser de una relación con más de dos elementos, sino debería\
	 * eliminarse la relación
	 * 	
	 * se actualizan todas las vistas de la relación
	 */
	public void eliminarEntidad(EntidadGraficable entidadGraficable) throws GraficableException
	{
		Iterator<EntidadRolGraficable> itEntidad;
		List<EntidadRol> listaEntidadesRol = new LinkedList<>();
		RelacionGraficable relacion;

		for(GraficableConEtiqueta graficable: relacionDecorada.getGraficables())
		{	
			relacion = (RelacionGraficable) graficable;
			itEntidad = relacion.getReferenciasExternas().iterator();
			
			while(itEntidad.hasNext())	// busco y borro la entidad a sacar
			{
				if (itEntidad.next().getEntidad().getObjetoDiagramaDecorado() == entidadGraficable.getObjetoDiagramaDecorado())
				{
					itEntidad.remove();
					break;
				}
			}
		}
		
		//vuelvo a setear la list de entidades en la lógica
		
		for(EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			listaEntidadesRol.add(entidadRol.getEntidadRol());
		
		try {
			relacionDecorada.setEntidadesRol(listaEntidadesRol);
		} catch (RelacionException e) {
			throw new GraficableException(e.getMessage());
		}
	}
	
	
	public EntidadRolGraficable getEntidadRol(Entidad entidad)
	{
		for(EntidadRolGraficable entidadRol: this.getReferenciasExternas())
			if(entidadRol.getEntidad().getObjetoDiagramaDecorado() == entidad)
				return entidadRol;
		
		return null;
	}
	
	@Override
	public void validar(ContenedorDeErrores errores)
	{
		super.validar(errores);

		//alguna otra cosa q haya q validar
	}

}